package com.fengwk.support.math;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import com.fengwk.support.exception.BaseException;

/**
 * 一元一次方程
 * 
 * @author fengwk
 *
 */
public class Equation11 implements Serializable {
	
	private static final long serialVersionUID = -8104122589966087875L;
	
	private double x1, y1, x2, y2;// 两点
	private Double a, b;// a * x + b = y || x = a || y = b
	private double k;// 斜率
	
	public static Equation11 build(P p, double k) {
		return build(p.x, p.y, k);
	}
	
	public static Equation11 build(double[] p, double k) {
		return build(p[0], p[1], k);
	}
	
	public static Equation11 build(double x, double y, double k) {
		return new Equation11(x, y, k);
	}
	
	public static Equation11 build(P p1, P p2) {
		return build(p1.x, p1.y, p2.x, p2.y);
	}
	
	public static Equation11 build(double[] p1, double[] p2) {
		return build(p1[0], p1[1], p2[0], p2[1]);
	}
	
	public static Equation11 build(double x1, double y1, double x2, double y2) {
		if (x1 == x2 && y1 == y2)
			return null;
		return new Equation11(x1, y1, x2, y2);
	}
	
	public Equation11(double x, double y, double k) {
		this.x1 = x;
		this.y1 = y;
		this.k = k;
		if (k == Double.POSITIVE_INFINITY) {
			a = x;
			x2 = a;
			y2 = y + 1;
		} else if (k == 0) {
			b = y;
			x2 = x + 1;
			y2 = b;
		} else {
			a = k;
			b = y - k * x;
			x2 = x + 1;
			y2 = fx(x2);
		}
	}
	
	public Equation11(Double a, Double b) {
		this.a = a;
		this.b = b;
		initK();
		// 赋予两点初始值
		if (a == null) {
			x1 = 0;
			y1 = b;
			x2 = 1;
			y2 = b;
		} else if (b == null) {
			x1 = a;
			y1 = 0;
			x2 = a;
			y2 = 1;
		} else {
			x1 = 0;
			y1 = fx(x1);
			x2 = 1;
			y2 = fx(x2);
		}
	}
	
	public Equation11(double x1, double y1, double x2, double y2) {
		if (x1 == x2 && y1 == y2)
			throw new BaseException("can't use same points");
		this.x1 = x1;
		this.y1 = y1;
		this.x2 = x2;
		this.y2 = y2;
		// 求得a * x + b = y方程式
		if (x1 == x2) {
			a = x1;
		} else if (y1 == y2) {
			b = y1;
		} else {
			a = (y2 - y1) / (x2 - x1);
			b = (x2 * y1 - x1 * y2) / (x2 - x1);
		}
		// 计算斜率
		initK();
	}
	
	private void initK() {
		if (b == null)
			k = Double.POSITIVE_INFINITY;
		else if (a == null)
			k = 0;
		else 
			k = a;
	}
	
	/**
	 * 若方程通过两点构造,则返回x1
	 * 
	 * @return
	 */
	public double getX1() {
		return x1;
	}
	
	/**
	 * 若方程通过两点构造,则返回y1
	 * 
	 * @return
	 */
	public double getY1() {
		return y1;
	}

	/**
	 * 若方程通过两点构造,则返回x2
	 * 
	 * @return
	 */
	public double getX2() {
		return x2;
	}

	/**
	 * 若方程通过两点构造,则返回y2
	 * 
	 * @return
	 */
	public double getY2() {
		return y2;
	}
	
	/**
	 * 若方程通过两点构造,获取点1
	 * 
	 * @return
	 */
	public P getP1() {
		return new P(x1, y1);
	}
	
	/**
	 * 若方程通过两点构造,获取点2
	 * 
	 * @return
	 */
	public P getP2() {
		return new P(x2, y2);
	}
	
	/**
	 * 获取左侧的点
	 * 
	 * @return
	 */
	public P getLeftP() {
		if (x1 < x2)
			return getP1();
		return getP2();
	}
	
	/**
	 * 获取右侧的点
	 * 
	 * @return
	 */
	public P getRightP() {
		if (x1 < x2)
			return getP2();
		return getP1();
	}
	
	/**
	 * 获取上方的点
	 * 
	 * @return
	 */
	public P getTopP() {
		if (y1 < y2)
			return getP2();
		return getP1();
	}
	
	/**
	 * 获取下方的点
	 * 
	 * @return
	 */
	public P getBottomP() {
		if (y1 < y2)
			return getP1();
		return getP2();
	}

	/**
	 * 方程表示参数a
	 * 
	 * @return
	 */
	public Double getA() {
		return a;
	}

	/**
	 * 方程表示参数b
	 * 
	 * @return
	 */
	public Double getB() {
		return b;
	}
	
	/**
	 * 获取斜率,垂直时认为无穷大,水平时认为0
	 * 
	 * @return
	 */
	public double k() {
		return k;
	}
	
	/**
	 * 获取弧度
	 * 
	 * @return
	 */
	public double radian() {
		return Math.atan(k());
	}

	/**
	 * 获取角度
	 * 
	 * @return
	 */
	public double degrees() {
		return Math.toDegrees(radian());
	}
	
	/**
	 * 获取交点
	 * 
	 * @return
	 */
	public P cross(Equation11 e) {
		if (k == e.k())
			// 平行
			return null;
		double x, y;
		Double a1 = a, b1 = b, a2 = e.getA(), b2 = e.getB();
		if (a1 == null && b2 == null) {
			x = a2;
			y = b1;
		} else if (b1 == null && a2 == null) {
			x = a1;
			y = b2;
		} else if (a1 == null && b2 != null) {
			x = (b1 - b2) / a2;
			y = b1;
		} else if (b1 == null && a2 != null) {
			x = a1;
			y = a1 * a2 + b2;
		} else if (b1 != null && a2 == null) {
			x = (b2 - b1) / a1;
			y = b2;
		} else if (a1 != null && b2 == null) {
			x = a2;
			y = a1 * a2 + b1;
		} else {
			x = (b2 - b1) / (a1 - a2);
			y = (a1 * b2 - a2 * b1) / (a1 - a2);
		}
		return new P(x, y);
	}
	
	/**
	 * 计算垂足
	 * 
	 * @param x
	 * @param y
	 * @return
	 */
	public double[] footPoint(double x, double y) {
		if (a == null)
			return new double[] {x, b};
		if (b == null)
			return new double[] {a, y};
		double footX = (k() * y + x - k() * b) / (1 + k() * k());
		double footY = k() * footX + b;
		return new double[] {footX, footY};
	}
	
	/**
	 * 计算垂足
	 * 
	 * @param x
	 * @param y
	 * @return
	 */
	public double[] footPoint(double[] p) {
		return footPoint(p[0], p[1]);
	}
	
	/**
	 * 方程到点的距离
	 * 
	 * @param x
	 * @param y
	 * @return
	 */
	public double distanceP(double x, double y) {
		if (a == null)
			return Math.abs(y - b);
		else if (b == null)
			return Math.abs(x - a);
		else
			return Math.abs((a * x - y + b) / Math.sqrt(a * a + 1));
	}
	
	/**
	 * 方程到点的距离
	 * 
	 * @param p
	 * @return
	 */
	public double distanceP(double[] p) {
		return distanceP(p[0], p[1]);
	}
	
	/**
	 * 方程到点的距离
	 * 
	 * @param p
	 * @return
	 */
	public double distanceP(P p) {
		return distanceP(p.x, p.y);
	}
	
	/**
	 * 输入x获取y
	 * 
	 * @param x
	 * @return
	 */
	public Double fx(double x) {
		if (b == null)
			return null;
		if (a == null)
			return b;
		return k * x + b;
	}
	
	/**
	 * 输入y获取x
	 * 
	 * @param y
	 * @return
	 */
	public Double fy(double y) {
		if (a == null)
			return null;
		if (b == null)
			return a;
		return (y - b) / k;
	}
	
	/**
	 * 方程与矩形交点,可获取0-2个交点,若获取到2个交点将会由下向上的顺序排列,下边->左边->右边->上边
	 * 
	 * @param x0
	 * @param y0
	 * @param w
	 * @param h
	 * @return
	 */
	public List<P> crossRect(double x0, double y0, double w, double h) {
		P pt = getXyInTop(x0, y0, w, h),
		  pr = getXyInRight(x0, y0, w, h),
		  pb = getXyInBottom(x0, y0, w, h),
		  pl = getXyInLeft(x0, y0, w, h);
		List<P> p2 = new ArrayList<>();
		fillP2(p2, pb);
		fillP2(p2, pl);
		fillP2(p2, pr);
		fillP2(p2, pt);
		return p2;
	}
	
	private void fillP2(List<P> p2, P p) {
		if (p == null || p2.size() == 2)
			return;
		p2.add(p);
	}
	
	// 从上边获取xy
	private P getXyInTop(double x0, double y0, double w, double h) {
		Double x = fy(y0 + h);
		if(x != null && x >= x0 && x <= x0 + w)
			return new P(x, y0 + h);
		return null;
	}
	
	// 从右边获取xy
	private P getXyInRight(double x0, double y0, double w, double h) {
		Double y = fx(x0 + w);
		if(y != null && y >= y0 && y <= y0 + h)
			return new P(x0 + w, y);
		return null;
	}
	
	// 从下边获取xy
	private P getXyInBottom(double x0, double y0, double w, double h) {
		Double x = fy(y0);
		if(x != null && x >= x0 && x <= x0 + w)
			return new P(x, y0);
		return null;
	}
	
	// 从左边获取xy
	private P getXyInLeft(double x0, double y0, double w, double h) {
		Double y = fx(x0);
		if(y != null && y >= y0 && y <= y0 + h)
			return new P(x0, y);
		return null;
	}
	
	/**
	 * 是否平行
	 * 
	 * @param e
	 * @return
	 */
	public boolean isParallel(Equation11 e) {
		return cross(e) == null;
	}
	
	/**
	 * 获取垂线方程
	 * 
	 * @param x
	 * @param y
	 * @return
	 */
	public Equation11 perpendicular(double x, double y) {
		if (a == null) 
			return build(x, y, Double.POSITIVE_INFINITY);
		else if (b == null) 
			return build(x, y, 0);
		else 
			return build(x, y, - 1 / k);
	}
	
	/**
	 * 合并方程,拟合到中间线
	 * 
	 * @param e
	 * @return
	 */
	public Equation11 merge(Equation11 e) {
		P c = cross(e);
		double newK = Math.tan((radian() + e.radian()) / 2);
		if (c == null) {
			Equation11 pE = perpendicular(x1, y1);
			P cp = pE.cross(e);
			P mid = cp.mid(x1, y1);
			return build(mid, newK);
		} else 
			return build(c, newK);
	}
	
	@Override
	public String toString() {
		if (a == null)
			return "y = " + b;
		if (b == null)
			return "x = " + a;
		return k() + " * x + " + b;
	}
	
}
